package com.wenbing.miio.mi.device.remote;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.wenbing.miio.core.*;
import com.wenbing.miio.core.NetworkInterface;
import com.wenbing.miio.util.function.Callback;
import com.wenbing.miio.jsonrpc.JsonRpcError;
import com.wenbing.miio.jsonrpc.JsonRpcMessage;
import com.wenbing.miio.mi.MessageFrame;
import com.wenbing.miio.mi.MiDeviceUtil;
import com.wenbing.miio.mi.MiFrameLayer;
import com.wenbing.miio.util.function.IDGenerator;
import com.wenbing.miio.util.BytesUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author wen bing
 * @projectName: miio
 * @package: com.wenbing.miio.mi.device.remote
 * @className: A
 * @date 2024/09/20 17:39
 **/
public class MiClient implements Bootable {
    private Logger LOGGER = LoggerFactory.getLogger(getClass());
    /**
     * 请求缓存，配对请求和响应
     */
    private Map<Integer, MiRequest> requestCache = new ConcurrentHashMap<>();

    /**
     * hello响应回调
     */
    private Callback<NetworkLayerContext> helloRespCallback;

    /**
     * 远程接口
     */
    private NetworkInterface remoteNetworkInterface;

    /**
     * 回调处理线程/响应处理线程
     */
    private Thread callbackThread;

    /**
     * id生成器
     */
    private IDGenerator<MiClient, Integer> idGenerator;

    /**
     * hello请求的阻塞锁
     */
    private final Object helloLock;

    /**
     * 超时时间 ms
     */
    private long timeout;

    public MiClient(Callback<NetworkLayerContext> helloRespCallback, NetworkInterface remoteNetworkInterface) {
        this(helloRespCallback, remoteNetworkInterface, 3000);
    }

    public MiClient(Callback<NetworkLayerContext> helloRespCallback, NetworkInterface remoteNetworkInterface, long timeout) {
        this.helloRespCallback = helloRespCallback;
        this.remoteNetworkInterface = remoteNetworkInterface;
        this.helloLock = new Object();
        this.idGenerator = new IncreaseIDGenerator();
        this.timeout = timeout;
    }

    public void hello() throws InterruptedException, IOException {
        NetworkLayerContext context = MapNetworkLayerContext.rootContext(null, remoteNetworkInterface.getAddress());
        context.setAttribute(MiFrameLayer.ATTR_FLAGS, MessageFrame.FLAGS_HELLO);
        ByteBuffer byteBuffer = ByteBuffer.allocate(0);
        write(context, byteBuffer);
        synchronized (helloLock) {
            helloLock.wait(timeout);
        }
    }

    public JsonRpcMessage syncRequest(String method, Object... params) throws InterruptedException, IOException {
        JsonRpcMessage message = new JsonRpcMessage(this.idGenerator.generate(this), method, params);
        byte[] payload = JSONObject.toJSONBytes(message, SerializerFeature.DisableCircularReferenceDetect);
        NetworkLayerContext context = MapNetworkLayerContext.rootContext(null, remoteNetworkInterface.getAddress());
        ByteBuffer byteBuffer = ByteBuffer.wrap(payload);
        write(context, byteBuffer);

        return message.getId() == null ? null : waitforResponse(message, null, true);
    }

    public void request(Callback<JsonRpcMessage> callback, String method, Object... params) throws InterruptedException, IOException {
        JsonRpcMessage message = new JsonRpcMessage(idGenerator.generate(this), method, params);
        byte[] payload = JSONObject.toJSONBytes(message, SerializerFeature.DisableCircularReferenceDetect);
        NetworkLayerContext context = MapNetworkLayerContext.rootContext(null, remoteNetworkInterface.getAddress());
        ByteBuffer byteBuffer = ByteBuffer.wrap(payload);
        write(context, byteBuffer);

        waitforResponse(message, callback, false);
    }

    protected void handler(NetworkLayerContext context, ByteBuffer packingData) {
        if (packingData.limit() == 0) {
            if (helloRespCallback != null) {
                helloRespCallback.callback(context);
            }
            synchronized (helloLock) {
                helloLock.notifyAll();
            }
            return;
        }
        callback(packingData);
    }

    private JsonRpcMessage waitforResponse(JsonRpcMessage message, Callback<JsonRpcMessage> callback, boolean sync) throws InterruptedException {
        try {
            MiRequest miRequest = new MiRequest(message, sync);
            miRequest.setOnArrivedCallback(callback);
            requestCache.put(message.getId(), miRequest);
            return miRequest.waitfor();
        }
        catch (InterruptedException interruptedException) {
            requestCache.remove(message.getId());
            throw interruptedException;
        }
    }

    private void write(NetworkLayerContext context, ByteBuffer byteBuffer) throws IOException {
        RowFrame frame = new RowFrame(context, byteBuffer);
        remoteNetworkInterface.sendRowFrame(frame);
    }

    private void callback(ByteBuffer packingData) {
        int offset = packingData.arrayOffset();
        byte[] payload = Arrays.copyOfRange(packingData.array(), offset + packingData.position(), offset + packingData.limit());
        JsonRpcMessage jsonRpc = JSONObject.parseObject(payload, JsonRpcMessage.class);
        Integer id = jsonRpc.getId();
        MiRequest r = (id == null ? null : requestCache.remove(id));
        if (r != null) {
            r.callback(jsonRpc);
        }
    }

    @Override
    public void boot() {
        callbackThread = InnerThread.daemonThread("MiClient-handler", ()-> {
            RowFrame rowFrame = remoteNetworkInterface.receiveRowFrame();
            handler(rowFrame.getContext(), rowFrame.getByteBuffer());
        });
        callbackThread.start();
        LOGGER.info("{} start success", callbackThread.getName());
        LOGGER.info("MiClient boot success");
    }

    private class IncreaseIDGenerator implements IDGenerator<MiClient, Integer> {
        private int count;
        @Override
        public Integer generate(MiClient input) {
            return ++count;
        }
    }

    private class MiRequest {
        /**
         * 请求
         */
        private JsonRpcMessage message;


        /**
         * 响应回调
         */
        private Callback<JsonRpcMessage> onArrivedCallback;

        /**
         * 锁对象
         */
        private final Object lock;

        public MiRequest(JsonRpcMessage requestMessage, boolean sync) {
            this.lock = sync ? new Object() : null;
            this.message = requestMessage;
            this.message.setError(JsonRpcError.TIMEOUT);
        }

        /**
         * 异步请求时的回调函数
         * @param onArrivedCallback
         */
        public void setOnArrivedCallback(Callback<JsonRpcMessage> onArrivedCallback) {
            this.onArrivedCallback = onArrivedCallback;
        }

        public JsonRpcMessage waitfor() throws InterruptedException {
            if (!isSynchronized()) {
                return null;
            }
            synchronized (this.lock) {
                this.lock.wait(MiClient.this.timeout);
                return this.message;
            }
        }

        public void callback(JsonRpcMessage message) {
            if (!isSynchronized()) {
                if (onArrivedCallback != null) {
                    onArrivedCallback.callback(message);
                }
                return;
            }
            synchronized (this.lock) {
                this.message.setResult(message.getResult());
                this.message.setError(message.getError());
                this.lock.notify();
            }
        }

        /**
         * 是否为同步请求
         */
        private synchronized boolean isSynchronized() {
            return lock != null;
        }
    }
}
